package cn.migu.music.hangupsms.service.sftp;

import cn.migu.music.common.utils.LogUtils;
import cn.migu.music.hangupsms.common.config.SftpHostConfig;
import cn.migu.music.hangupsms.common.entity.BlackUserInfo;
import cn.migu.music.hangupsms.constants.EProvinceName;
import cn.migu.music.hangupsms.dbcache.BlackUserInfoMapper;
import cn.migu.music.hangupsms.model.BlackUserScanParam;
import cn.migu.music.hangupsms.repository.BlackUserInfoDbMapper;
import cn.migu.music.hangupsms.service.LocalIdService;
import cn.migu.music.hangupsms.utils.CompressUtils;
import cn.migu.music.hangupsms.utils.LocalFileUtil;
import com.jcraft.jsch.ChannelSftp;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.io.File;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 黑名单导出服务
 */
@Slf4j
@Service
public class BlackUserExportSftpService {
    /**
     * 导出类型：
     * 1：增量
     * 0：全量
     */
    public static final String            EXPORT_TYPE_ADD     = "1";
    public static final String            EXPORT_TYPE_ALL     = "0";
    /**
     * 增量操作类型：
     * 0： 新增
     * 1： 删除
     */
    private static final String           OPR_TYPE_ADD        = "0";
    private static final String           OPR_TYPE_DEL        = "1";
    private static final String           NEW_LINE            = "\n";
    private static final String           YYYY_MM_DD_HH_MM_SS = "yyyy-MM-dd HH:mm:ss";
    private static final String           YYYY_M_MDD          = "yyyyMMdd";
    private static final String           DEFAULT             = "default";

    @Autowired
    private BlackUserInfoDbMapper         blackUserInfoDbMapper;
    @Autowired
    private SftpHostConfig                sftpHostConfig;
    @Autowired
    private SftpService                   sftpService;
    @Autowired
    private DelBlackUserRedisCacheService delBlackUserRedisCacheService;
    @Autowired
    private LocalIdService                localIdService;
    @Autowired
    private BlackUserInfoMapper           blackUserInfoMapper;

    /**
     * 导出黑名单用户
     */
    public void exportBlackUser(String localId, String type, String startTime, String endTime) {
        if (StringUtils.isNotEmpty(localId)) {
            // 执行导出程序
            if (StringUtils.equals(type, EXPORT_TYPE_ADD)) {
                // 增量导出
                exportBlackUserByAdd(localId, startTime, endTime);
            } else if (StringUtils.equals(type, "test")) {
                // 测试文件上传用
                exportBlackUserByTestSftp(localId);
            } else {
                // 全量导出
                exportBlackUserByAll(localId);
            }
        }
    }

    private void exportBlackUserByAdd(String localId, String startTime, String endTime) {
        String timeStr = getTimeStr();
        String provinceShortName = DEFAULT;
        EProvinceName eProvinceName = EProvinceName.valueOfLocalId(localId);
        if (eProvinceName != null) {
            provinceShortName = eProvinceName.getShortName();
        }
        String localDir = sftpHostConfig.getLocalDir();

        // 1.查数据库增量黑名单
        // 2.查redis中删除的黑名单用户
        Set<File> filePathSet = new HashSet<>();

        try {
            // 写入txt文件
            scanAndWriteTxtForAdd(localId, timeStr, provinceShortName, localDir, filePathSet, startTime, endTime);

            if (filePathSet.isEmpty()) {
                LogUtils.info(log, "无增量黑名单，无需上传");
                // 无文件
                return;
            }

            // 压缩文件
            String password = sftpHostConfig.getCompressPdPrefix() + timeStr;
            String outFileName = sftpHostConfig.getOutFilePrefix() + timeStr + "_" + provinceShortName + ".zip";
            String outputFilePath = localDir + File.separator + outFileName;
            CompressUtils.compressAndEncryptFiles(filePathSet, password, outputFilePath);

            // 上传SFTP
            uploadFile(outputFilePath, outFileName);
        } catch (Exception ex) {
            LogUtils.error(ex, log, "导出增量黑名单异常");
        }
    }

    private void scanAndWriteTxtForAdd(String localId,
                                       String timeStr,
                                       String provinceShortName,
                                       String localDir,
                                       Set<File> filePathSet,
                                       String startTimeStr,
                                       String endTimeStr) {
        LocalDateTime startTime;
        LocalDateTime endTime;
        if (StringUtils.isNotEmpty(startTimeStr)) {
            startTime = LocalDateTime.parse(startTimeStr, DateTimeFormatter.ofPattern(YYYY_MM_DD_HH_MM_SS));
            endTime = LocalDateTime.parse(endTimeStr, DateTimeFormatter.ofPattern(YYYY_MM_DD_HH_MM_SS));
        } else {
            // 自动计算
            LocalDate today = LocalDate.now();
            LocalDate lastDay = today.plusDays(-1);
            LocalTime localTime = LocalTime.of(0, 0, 0);
            startTime = LocalDateTime.of(lastDay, localTime);
            endTime = LocalDateTime.of(today, localTime);
        }

        BlackUserScanParam blackUserScanParam = new BlackUserScanParam();
        blackUserScanParam.setLocalId(localId);
        blackUserScanParam.setTimeStr(timeStr);
        blackUserScanParam.setNums(0L);
        blackUserScanParam.setFileNum(1);
        blackUserScanParam.setLimitSize(sftpHostConfig.getScanLimit());
        blackUserScanParam.setPhoneNumber("0");
        blackUserScanParam.setStartTime(startTime);
        blackUserScanParam.setEndTime(endTime);

        boolean completeScanAdd = false;
        boolean completeScanDelCache = false;
        Map<String, Long> fileRowCountMap = new HashMap<>();
        while (!completeScanDelCache) {
            List<String> blackUserList = new ArrayList<>();
            // 获取增量数据
            completeScanDelCache = getBlackUserForAdd(completeScanAdd, blackUserList, blackUserScanParam);
            if (CollectionUtils.isNotEmpty(blackUserList)) {
                // 增量写文件
                batchWriteForAdd(provinceShortName, localDir, filePathSet, blackUserScanParam, completeScanDelCache, blackUserList, fileRowCountMap);

                if (!completeScanAdd && blackUserList.size() < blackUserScanParam.getLimitSize()) {
                    completeScanAdd = true;
                }
            } else {
                completeScanAdd = true;
            }
        }

        // 打印日志
        logForTxtCount(fileRowCountMap);
    }

    private void logForTxtCount(Map<String, Long> fileRowCountMap) {
        for (Map.Entry<String, Long> entry : fileRowCountMap.entrySet()) {
            LogUtils.info(log, "黑名单导出计数-文件:%s,数量:%s", entry.getKey(), String.valueOf(entry.getValue()));
        }
    }

    private void batchWriteForAdd(String provinceShortName,
                                  String localDir,
                                  Set<File> filePathSet,
                                  BlackUserScanParam blackUserScanParam,
                                  boolean completeScanDelCache,
                                  List<String> blackUserList,
                                  Map<String, Long> fileRowCountMap) {
        for (String msisdn : blackUserList) {
            blackUserScanParam.setPhoneNumber(msisdn);
            // 写入本地文件
            // 是需要的localId
            wiriteTxtForAdd(blackUserScanParam.getTimeStr(), provinceShortName, localDir, filePathSet, blackUserScanParam.getFileNum(), msisdn,
                completeScanDelCache);
            // 计数
            blackUserScanParam.setNums(blackUserScanParam.getNums() + 1);
            // 文件行数记录
            fileRowCount(provinceShortName, blackUserScanParam, fileRowCountMap);
            if (blackUserScanParam.getNums() >= sftpHostConfig.getTxtSplitNumLimit()) {
                // 输出行数已达上限10万，生成新的文件序号
                blackUserScanParam.setFileNum(blackUserScanParam.getFileNum() + 1);
                blackUserScanParam.setNums(0L);
            }
        }
    }

    private void wiriteTxtForAdd(String timeStr,
                                 String provinceShortName,
                                 String localDir,
                                 Set<File> filePathSet,
                                 int fileNum,
                                 String phoneNumber,
                                 boolean completeScanDelCache) {
        String oprType = !completeScanDelCache ? OPR_TYPE_ADD : OPR_TYPE_DEL;
        StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.append(phoneNumber);
        stringBuilder.append("|");
        stringBuilder.append(oprType);
        stringBuilder.append(NEW_LINE);
        // 开始写入txt文本
        Path localFilePath = getWriteFilePath(provinceShortName, timeStr, fileNum, filePathSet, localDir);
        try {
            FileUtils.write(localFilePath.toFile(), stringBuilder.toString(), Charset.defaultCharset(), true);

        } catch (IOException e) {
            LogUtils.error(e, log, "写文件:%s", localFilePath.toString());
        }
    }

    private boolean getBlackUserForAdd(boolean completeScanAdd,
                                       List<String> blackUserList,
                                       BlackUserScanParam blackUserScanParam) {
        if (!completeScanAdd) {
            // 获取新增数据
            List<BlackUserInfo> blackUserInfoList1 = blackUserInfoDbMapper.selectByAdd(blackUserScanParam.getPhoneNumber(),
                Integer.parseInt(blackUserScanParam.getLocalId()), blackUserScanParam.getStartTime(), blackUserScanParam.getEndTime(),
                blackUserScanParam.getLimitSize());

            List<String> list = blackUserInfoList1.stream().map(BlackUserInfo::getPhonenumber).collect(Collectors.toList());
            blackUserList.addAll(list);
            return false;
        } else {
            // 获取删除数据
            Set<String> delPhoneSet = new HashSet<>();
            // 从redis读取
            getDelBlackUserFromRedis(delPhoneSet, blackUserScanParam.getStartTime(), blackUserScanParam.getEndTime());
            if (CollectionUtils.isNotEmpty(delPhoneSet)) {
                for (String phoneNumber : delPhoneSet) {
                    BlackUserInfo blackUserInfo = blackUserInfoMapper.queryBlackUserInfo(phoneNumber);
                    if (null == blackUserInfo && StringUtils.equals(blackUserScanParam.getLocalId(), localIdService.getLocalIdStr(phoneNumber))) {
                        // 非黑名单 且 localId 为目标省份，则加入增量导出列表
                        blackUserList.addAll(delPhoneSet);
                    }
                }
            }
            return true;
        }
    }

    private void getDelBlackUserFromRedis(Set<String> delPhoneSet, LocalDateTime startTime, LocalDateTime endTime) {
        String endTimeDate = endTime.toLocalDate().format(DateTimeFormatter.ofPattern(YYYY_M_MDD));
        LocalDate processDate = startTime.toLocalDate();
        String dateStr = processDate.format(DateTimeFormatter.ofPattern(YYYY_M_MDD));
        while (dateStr.compareTo(endTimeDate) < 0) {
            Set<String> phoneSet = delBlackUserRedisCacheService.getAllDelBlackUserByDate(dateStr);
            delPhoneSet.addAll(phoneSet);
            // 天数加1
            processDate = processDate.plusDays(1L);
            dateStr = processDate.format(DateTimeFormatter.ofPattern(YYYY_M_MDD));
        }
    }

    private void exportBlackUserByAll(String localId) {
        String timeStr = getTimeStr();
        String provinceShortName = DEFAULT;
        EProvinceName eProvinceName = EProvinceName.valueOfLocalId(localId);
        if (eProvinceName != null) {
            provinceShortName = eProvinceName.getShortName();
        }
        String localDir = sftpHostConfig.getLocalDir();
        Set<File> filePathSet = new HashSet<>();

        try {
            // 写入txt文件
            scanAndWriteDataToTxtForAll(localId, timeStr, provinceShortName, localDir, filePathSet);

            if (filePathSet.isEmpty()) {
                LogUtils.info(log, "无黑名单，无需上传");
                // 无文件
                return;
            }

            // 压缩文件
            String password = sftpHostConfig.getCompressPdPrefix() + timeStr;
            String outFileName = sftpHostConfig.getOutFilePrefix() + timeStr + "_" + provinceShortName + ".zip";
            String outputFilePath = localDir + File.separator + outFileName;
            CompressUtils.compressAndEncryptFiles(filePathSet, password, outputFilePath);

            // 上传SFTP
            uploadFile(outputFilePath, outFileName);
        } catch (Exception ex) {
            LogUtils.error(ex, log, "导出全量黑名单异常");
        }
    }

    private void scanAndWriteDataToTxtForAll(String localId, String timeStr, String provinceShortName, String localDir, Set<File> filePathSet) {
        BlackUserScanParam blackUserScanParam = new BlackUserScanParam();
        blackUserScanParam.setLocalId(localId);
        blackUserScanParam.setTimeStr(timeStr);
        blackUserScanParam.setNums(0L);
        blackUserScanParam.setFileNum(1);
        blackUserScanParam.setLimitSize(sftpHostConfig.getScanLimit());
        blackUserScanParam.setPhoneNumber("0");
        Map<String, Long> fileRowCountMap = new HashMap<>();
        while (true) {
            List<BlackUserInfo> blackUserInfoList = blackUserInfoDbMapper.selectByAll(blackUserScanParam.getPhoneNumber(),
                Integer.parseInt(blackUserScanParam.getLocalId()), blackUserScanParam.getLimitSize());

            if (CollectionUtils.isNotEmpty(blackUserInfoList)) {
                batchWriteForAll(provinceShortName, localDir, filePathSet, blackUserScanParam, blackUserInfoList, fileRowCountMap);
            }

            if (CollectionUtils.isEmpty(blackUserInfoList) || blackUserInfoList.size() < blackUserScanParam.getLimitSize()) {
                break;
            }
        }

        // 打印日志
        logForTxtCount(fileRowCountMap);
    }

    private void batchWriteForAll(String provinceShortName,
                                  String localDir,
                                  Set<File> filePathSet,
                                  BlackUserScanParam blackUserScanParam,
                                  List<BlackUserInfo> blackUserInfoList,
                                  Map<String, Long> fileRowCountMap) {
        for (BlackUserInfo blackUserInfo : blackUserInfoList) {
            blackUserScanParam.setPhoneNumber(blackUserInfo.getPhonenumber());
            // 写入本地文件
            writeTxtForAll(blackUserScanParam.getTimeStr(), provinceShortName, localDir, filePathSet, blackUserScanParam.getFileNum(),
                blackUserInfo.getPhonenumber());

            // 计数
            blackUserScanParam.setNums(blackUserScanParam.getNums() + 1);
            // 文件行数记录
            fileRowCount(provinceShortName, blackUserScanParam, fileRowCountMap);
            if (blackUserScanParam.getNums() >= sftpHostConfig.getTxtSplitNumLimit()) {
                // 输出行数已达上限10万，生成新的文件序号
                blackUserScanParam.setFileNum(blackUserScanParam.getFileNum() + 1);
                blackUserScanParam.setNums(0L);
            }
        }
    }

    private void fileRowCount(String provinceShortName, BlackUserScanParam blackUserScanParam, Map<String, Long> fileRowCountMap) {
        String fileName = getFileName(provinceShortName, blackUserScanParam.getTimeStr(), blackUserScanParam.getFileNum());
        fileRowCountMap.put(fileName, blackUserScanParam.getNums());
    }

    private void writeTxtForAll(String timeStr,
                                String provinceShortName,
                                String localDir,
                                Set<File> filePathSet,
                                int fileNum,
                                String phoneNumber) {
        // 是需要的localId
        StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.append(phoneNumber);
        stringBuilder.append("|");
        stringBuilder.append(OPR_TYPE_ADD);
        stringBuilder.append(NEW_LINE);
        // 开始写入txt文本
        Path localFilePath = getWriteFilePath(provinceShortName, timeStr, fileNum, filePathSet, localDir);
        try {
            FileUtils.write(localFilePath.toFile(), stringBuilder.toString(), Charset.defaultCharset(), true);
        } catch (IOException e) {
            LogUtils.error(e, log, "写文件:%s", localFilePath.toString());
        }
    }

    /**
     * 获取时间串
     * 
     * @return
     */
    private String getTimeStr() {
        LocalDateTime now = LocalDateTime.now();
        return now.format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss"));
    }

    private Path getWriteFilePath(String provinceShortName, String timeStr, int fileNum, Set<File> filePathSet, String localDir) {
        String fileName = getFileName(provinceShortName, timeStr, fileNum);
        Path localFilePath = Paths.get(localDir + File.separator + fileName);
        // 文件
        filePathSet.add(localFilePath.toFile());
        return localFilePath;
    }

    private String getFileName(String provinceShortName, String timeStr, int fileNum) {
        String serialNumber = StringUtils.leftPad(String.valueOf(fileNum), 4, "0");
        return provinceShortName + "_I_" + timeStr + "_" + serialNumber + ".txt";
    }

    private List<String> uploadFile(String compressFilePath, String fileName) {
        List<String> uploadFileInfos = new ArrayList<>();

        ChannelSftp channelSftp = null;
        try {
            channelSftp = sftpService.getsftp(sftpHostConfig.getHost(), sftpHostConfig.getPort(), sftpHostConfig.getUserName(),
                sftpHostConfig.getPasswd());
            String remoteDir = sftpHostConfig.getRemoteDir();
            String remoteFilePath = remoteDir + File.separator + fileName;
            if (!sftpService.isExistDir(remoteDir, channelSftp)) {
                sftpService.createDir(channelSftp, remoteDir);
            }

            doUploadOneFile(uploadFileInfos, channelSftp, compressFilePath, remoteFilePath, fileName);
            LogUtils.info(log, "挂机黑名单-上传文件成功,remoteFilePath:%s ", remoteFilePath);
        } catch (Exception e) {
            LogUtils.error(e, log, "upload file error");
        } finally {
            sftpService.disconnect(channelSftp);
        }
        return uploadFileInfos;
    }

    private void doUploadOneFile(List<String> uploadFileInfos,
                                 ChannelSftp channelSftp,
                                 String localFilePath,
                                 String remoteFilePath,
                                 String fileName) {
        try {
            sftpService.uploadFile(channelSftp, localFilePath, remoteFilePath);
            LogUtils.info(log, "upload file success:%s->%s", localFilePath, remoteFilePath);
            uploadFileInfos.add(fileName + ":success");
        } catch (Exception e) {
            LogUtils.error(e, log, "upload file error:%s->%s", localFilePath, remoteFilePath);
            uploadFileInfos.add(fileName + ":failed");
        } finally {
            // 删除压缩文件
            LocalFileUtil.deleteLocalFile(localFilePath);
        }
    }

    private void exportBlackUserByTestSftp(String localId) {
        String timeStr = getTimeStr();
        String provinceShortName = DEFAULT;
        EProvinceName eProvinceName = EProvinceName.valueOfLocalId(localId);
        if (eProvinceName != null) {
            provinceShortName = eProvinceName.getShortName();
        }
        String localDir = sftpHostConfig.getLocalDir();
        Set<File> filePathSet = new HashSet<>();

        try {
            // 写入本地文件
            writeTxtForAll(timeStr, provinceShortName, localDir, filePathSet, 1, "18200280062");

            // 压缩文件
            String password = sftpHostConfig.getCompressPdPrefix() + timeStr;
            String outFileName = sftpHostConfig.getOutFilePrefix() + timeStr + "_" + provinceShortName + ".zip";
            String outputFilePath = localDir + File.separator + outFileName;
            CompressUtils.compressAndEncryptFiles(filePathSet, password, outputFilePath);

            // 上传SFTP
            uploadFile(outputFilePath, outFileName);
        } catch (Exception ex) {
            LogUtils.error(ex, log, "导出测试黑名单异常");
        }
    }
}
